"""flask app配置、定义、扩展"""
import requests
import traceback
import os, shutil
from queue import Queue
from src.api import proxy
from src.mod import Search
from src.core import Enums
from functools import wraps
from src.db import dbm, dbs
from flask_cors import CORS
from threading import Thread
from datetime import timedelta
from src.core import log, Enums
from flask import Flask, request
from pydantic import ValidationError
from src.core.variables.info import sof
from src.core.read_conf import get_conf
from src.core.variables.status import *
from src.core.variables.constant import *
from sqlalchemy.orm import scoped_session
from src.core.variables.info import VERSION
from flask_jwt_extended import jwt_required
from src.db.library.sys_default import Node
from flask_jwt_extended import verify_jwt_in_request
from flask_jwt_extended import JWTManager, jwt_required
from src.core.variables.exception import MAIN_EXCEPTION
from flask_jwt_extended import JWTManager, get_jwt_identity
from jwt.exceptions import ExpiredSignatureError, DecodeError
from flask_jwt_extended.exceptions import NoAuthorizationError


class FlaskConfig:
    """flask api的配置信息"""

    # 应用配置
    SECRET_KEY = os.urandom(32).hex()
    log.DEBUG(f"SECRET_KEY: {SECRET_KEY}")
    JWT_SECRET_KEY = get_conf("auth.key")
    JWT_ALGORITHM = "HS256"
    # JWT ACCESS TOKEN的有效期
    JWT_ACCESS_TOKEN_EXPIRES = timedelta(seconds=int(get_conf("auth.access_time_out")))
    # JWT REFRESH TOKEN的有效期
    JWT_REFRESH_TOKEN_EXPIRES = timedelta(
        seconds=int(get_conf("auth.refresh_time_out"))
    )
    # 安全配置
    JWT_TOKEN_LOCATION = ["headers"]
    JWT_HEADER_NAME = "Authorization"
    JWT_HEADER_TYPE = "Bearer"
    JWT_BLACKLIST_ENABLED = True
    JWT_BLACKLIST_TOKEN_CHECKS = ["access", "refresh"]
    # JWT_SLIDING_EXPIRATION = True  # 启用滑动过期机制
    # JWT_SLIDING_EXPIRATION_DELTA = timedelta(seconds=int(get_conf("auth.access_time_out")))


app: Flask = Flask(__name__, static_folder=f"{os.getcwd()}/Static", static_url_path="")
# 权限
app.config.update(vars(FlaskConfig))
# 鉴权
jwt: JWTManager = JWTManager(app)
# 允许跨域
cors: CORS = CORS(app)
# token黑名单
blacklist = set()


@jwt.token_in_blocklist_loader
def check_if_token_in_blacklist(jwt_header, jwt_payload):
    jti = jwt_payload["jti"]
    return jti in blacklist


def request_record(result_print=True, func_name="", status='enable'):
    def coat(func):
        """请求过程中的日志记录"""

        @wraps(func)
        def wrapper(*args, **kwargs):
            if status == 'enable':
                if request.headers.getlist("Content-Type") == ["application/json"]:
                    try:
                        log.DEBUG(f"请求内容: {request.get_json()}")
                    except Exception as e:
                        log.DEBUG(f"请求内容不是有效的JSON格式: {str(e)}")
                else:
                    if request.method != "OPTIONS":
                        log.DEBUG(f"请求内容中不含json数据！")
                result = func(*args, **kwargs)
                result_0, result_1 = result
                log.DEBUG(
                    f"【{request.remote_addr}】使用【{request.user_agent}】请求：{request.method} {request.path}"
                )
                match CONF.LOG_LEVEL:
                    case Enums.LogLevel.DEBUG:
                        if result_print:
                            log.DEBUG(f"请求结果：{result_1} {result_0}")
                        elif func_name in ["jar_file_content"]:
                            log.DEBUG(f"请求结果：{result_1} [jar_file_content] 请求成功！")
                    case Enums.LogLevel.INFO:
                        log.INFO(
                            f"【{request.remote_addr}】使用【{request.user_agent}】请求本服务"
                        )
                        log.INFO(f"请求结果：{result_1}")
                return (result_0, result_1)
            else:
                return func(*args, **kwargs)

        return wrapper

    return coat


def admin_permission(func):
    """admin角色权限装饰器"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        current_username = get_jwt_identity()
        enum, user = Search.Core.user_by_username(current_username)
        match enum:
            case (
                Enums.SearchResult.NOT_FOUND
                | Enums.SearchResult.MULTIPLE_FOUND
                | Enums.SearchResult.ERROR
            ):
                log.WARNING(f"查找用户ID错误！")
            case Enums.SearchResult.SUCCESS:
                log.INFO(
                    f"【{current_username}|{user.uid}】 操作 {request.method} {request.path}"
                )
                if user.roles is None or "admin" not in eval(user.roles):
                    log.WARNING(f"权限不足！")
                    return ERROR_CODE_CUSTOMIZE("权限不足！", 403)
                return func(user=user, *args, **kwargs)

    return wrapper


def capture_exception(func):
    """捕获异常装饰器"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except ValidationError as e:
            result = {}
            for i in e.errors():
                if i["msg"] == "Field required":
                    result[i["loc"][0]] = "必填"
                elif i["msg"] == "Input should be a valid string":
                    result[i["loc"][0]] = "输出值类型错误，应当为字符串"
                elif i["msg"] == "String should have at least 1 character":
                    result[i["loc"][0]] = "必填"
                elif "Value error, " in i["msg"]:
                    result[i["loc"][0]] = i["msg"].split("Value error, ")[1]
                else:
                    result[i["loc"][0]] = i["msg"]
            return ERROR_CODE.VERIFICATION_FAILDED(result)
        except MAIN_EXCEPTION as e:
            return ERROR_CODE_CUSTOMIZE(f"控制失败: {e.message}", e.code)
        except ExpiredSignatureError as e:
            log.WARNING(f"【请求路由：{request.path}】Token过期！")
            return ERROR_CODE.EXPIRED_TOKEN()
        except NoAuthorizationError as e:
            log.WARNING(f"【请求路由：{request.path}】未携带Token")
            return ERROR_CODE.NO_AUTHORIZATION()
        except DecodeError as e:
            log.WARNING(f"【请求路由：{request.path}】Token过期！")
            return ERROR_CODE.EXPIRED_TOKEN()
        except Exception as e:
            # 打印完整的错误位置和堆栈
            traceback.print_exc()
            result = e
            match CONF.LOG_LEVEL:
                case Enums.LogLevel.DEBUG:
                    result = e
                case Enums.LogLevel.INFO:
                    result = e.__class__
            return ERROR_CODE.INTERNAL_SERVER_ERROR(result)

    return wrapper


def whats_stype(hosting=False):
    """判断请求类型装饰器"""

    def cost(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if hosting:
                if request.args.get("stype") in ["java", "Java"]:
                    if not sof.java:
                        return ERROR_CODE.SERVICE_UNAVAILABLE(
                            "Java应用服务托管功能未启用！", 505
                        )
                    return func(stype=Enums.Hosting.Stypes.JAVA, *args, **kwargs)
                elif request.args.get("stype") in ["static", "Static"]:
                    if not sof.static:
                        return ERROR_CODE.SERVICE_UNAVAILABLE(
                            "前端静态文件托管功能未启用！", 505
                        )
                    return func(stype=Enums.Hosting.Stypes.STATIC, *args, **kwargs)
                else:
                    return ERROR_CODE.METHOD_NOT_ALLOWED()
            else:
                return func(*args, **kwargs)

        return wrapper

    return cost


def mn_check(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        with dbm.get("sys_default") as db_session:
            # fmcac：
            #   全称：    fisherman multi_nodes communication authentication certificate
            #   中文释义： 渔翁多节点通讯认证证书
            if db_session.query(dbs.sys_default.Node).filter(
                dbs.sys_default.Node.certificate == request.headers.get("fmcac")
            ):
                return SUCCESS_CODE.NODES_CERT_OK()
            else:
                return ERROR_CODE.SERVICE_UNAVAILABLE("认证错误或节点不存在！", 401)

    return wrapper


def multi_nodes_request(func, node_id: str = "", *args, **kwargs):
    """
    多节点同步请求
    @param func: 原api实现函数
    @param node_id: 节点ID，为空时返回所有节点
    """
    result = []
    with dbm.get(f"sys_default") as db_session:
        db_session: scoped_session
        if db_session.query(dbs.sys_default.Node).count() == 1:
            return result
        q = Queue()
        try:
            request_data = request.get_json()
        except Exception:
            request_data = None
        request_path = request.path
        args = request.args
        from src.db.library.sys_default import Node

        def all_send():
            def send_request(
                node: "Node",
            ):
                try:
                    req_args = "?"
                    for index, value in args.items():
                        if req_args != "?":
                            req_args += "&"
                        req_args += f"{index}={value}"
                    url = f"http://{node.ip}:{node.port}{request_path}" + req_args
                    res = requests.post(
                        url=url,
                        json=request_data,
                        headers={
                            "Content-Type": "application/json",
                            "fmcac": str(json.dumps(eval(node.certificate))),
                            "User-Agent": f"FisherMan/{VERSION}",
                        },
                    ).json()
                    q.put(res)
                except Exception as e:
                    log.WARNING(f"节点<{node.ip}>请求失败: {e}")
                    q.put(
                        {
                            "message": "节点请求失败",
                            "status": "error",
                            "status_code": "NODES_REQUEST_FAILED",
                        }
                    )
            for i in db_session.query(dbs.sys_default.Node).all():
                if i.host != "localhost":
                    Thread(
                        target=send_request, args=(i,), name=f"mn_request_<{i.ip}>"
                    ).start()

        def loop():
            while True:
                temp = q.get()
                result.append(temp)
                if (
                    len(result)
                    == dbm.get(f"sys_default").query(dbs.sys_default.Node).count() - 1
                ):
                    break
        if node_id == "" or node_id is None:
            mn_loop = Thread(target=loop, name="mn_loop")
            mn_loop.start()
            Thread(target=all_send, name="mn_send").start()
            mn_loop.join()
            for i in result:
                if isinstance(i, dict) and i.get("status") == "error":
                    log.ERROR(f"节点请求失败！")
            return result
        node = (
            db_session.query(dbs.sys_default.Node)
            .filter(dbs.sys_default.Node.node_id == node_id)
            .first()
        )
        db_result = (
            db_session.query(dbs.sys_default.Node)
            .filter(dbs.sys_default.Node.node_id == node_id)
            .first()
        )
        if db_result:
            if db_result.host == "localhost":
                verify_jwt_in_request()
                current_username = get_jwt_identity()
                _, user = Search.Core.user_by_username(current_username)
                return func(user=user, *args, **kwargs)
            if request.url in PROXY_URL:
                return proxy_request(node)(func)(*args, **kwargs)
            Thread(target=all_send, name="mn_send", args=()).start()
            end_result = [q.get()]
            return end_result
        return ERROR_CODE.NODE_NOT_FOUND()


def result_merge(remote_result, func, *args, **kwargs):
    """
    多节点请求结果合并
    @param remote_result: 远程结果
    @param multi_nodes_request: 多节点请求函数
    @param func: 原api实现函数
    """
    A, B = jwt_required()(admin_permission(func))(*args, **kwargs)
    local_result = eval(A)
    for i in remote_result:
        if i["status"] == "success":
            for j_index, j_value in i["message"].items():
                local_result["message"][j_index] = j_value
    return local_result, B


def basic_decorator(hosting=False, result=True, multi_nodes=False, process_record=True):
    """
    减少重复代码的装饰器
    功能包括：
        - 捕获异常
        - 记录请求信息
        - JWT 认证/节点认证
        - 请求类型判断
        - 管理员权限校验
    @parm hosting: 是否是托管模块的请求
    @parm result: 是否输出请求的结果
    @parm multi_nodes: 是否支持多节点请求
    @parm process_record: 是否处理请求记录
    """

    def coat(func):
        func_name = func.__name__
        request_record_status = 'enable' if process_record else 'disable'
        @wraps(func)
        @capture_exception
        @request_record(result, func_name, status=request_record_status)
        @whats_stype(hosting)
        def wrapper(*args, **kwargs):
            # 动态添加认证装饰器
            if multi_nodes:
                node = Search.Core.node_by_node_id(request.args.get("node_id", ""))
                node_id = node.node_id if hasattr(node, "node_id") else None
                remote_result = None
                # 本地请求
                if "fmcac" in request.headers:
                    # 节点认证模式
                    check_result, _ = mn_check(func)(*args, **kwargs)
                    if eval(check_result)["status"] == "success":
                        with dbm.get("sys_default") as db_session:
                            db_session: scoped_session
                            db_result = (
                                db_session.query(dbs.sys_default.User)
                                .filter(dbs.sys_default.User.username == "multi_nodes")
                                .first()
                            )
                            if db_result:
                                return func(user=db_result, *args, **kwargs)
                            else:
                                log.ERROR(f"节点认证用户不存在！")
                                return ERROR_CODE_CUSTOMIZE(f"节点认证用户不存在！")
                    else:
                        return check_result
                # 远程请求
                else:
                    if node is not None:
                        if node.host == 'localhost':
                            return jwt_required()(admin_permission(func))(*args, **kwargs)
                    remote_result = multi_nodes_request(
                        func, node_id=node_id, *args, **kwargs
                    )
                    if node_id is None:
                        return result_merge(remote_result, func, *args, **kwargs)
                    else:
                        return remote_result[0], 200
            # 用户认证模式
            return jwt_required()(admin_permission(func))(*args, **kwargs)

        return wrapper

    return coat


def clear_temp(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        if eval(str(result[0]))["status"] == "success":
            if os.path.exists(CONF.PATH.TEMP):
                shutil.rmtree(CONF.PATH.TEMP)
                os.mkdir(CONF.PATH.TEMP)
                log.INFO(f"清理缓存目录")
        return result

    return wrapper


def proxy_request(node: Node):
    def coat(func):
        """多节点api代理"""
        @wraps(func)
        def wrapper(*args, **kwargs):
            headers = request.headers
            headers.pop("Authorization")
            headers['fmcac'] = str(json.dumps(eval(node.certificate)))
            req_args = "?"
            for index, value in request.args.items():
                if req_args != "?":
                    req_args += "&"
                req_args += f"{index}={value}"
            url = f"http://{node.ip}:{node.port}{request.path}" + req_args
            match request.path:
                case '/api/apps/hosting/download_file':
                    return proxy.download_file(url)
                case '/api/apps/hosting/realog':
                    return proxy.log_sse(url)
                case '/api/apps/hosting/receive_file':
                    return proxy.receive_file(url)
                case _:
                    return ERROR_CODE.PROXY_URL_NOT_FOUND()
        return wrapper
    return coat